package com.example.lt.schedul;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.lt.constant.AuditStatus;
import com.example.lt.constant.RedisConstant;
import com.example.lt.entity.video.HotVideo;
import com.example.lt.entity.video.Video;
import com.example.lt.service.SettingService;
import com.example.lt.service.video.VideoService;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Author 小涛Tao
 * @Date: 2024/08/12/11:38
 * version 1.0 注释：
 **/
@Component
@RequiredArgsConstructor
public class HotRank {

    private final VideoService videoService;

    private final RedisTemplate redisTemplate;

    private final SettingService settingService;

    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

    ObjectMapper om = new ObjectMapper();

    {
        jackson2JsonRedisSerializer.setObjectMapper(om);
    }

    /**
     * 定时计算视频热度，并更新热度排行榜
     */
    @Scheduled(cron = "0 0 */1 * * ?") // 每小时执行一次
    //@Scheduled(cron = "0/10 * * * * ?") // 每十秒执行一次
    public void hotRank() {
        // 控制数量
        final TopK topK = new TopK(11, new PriorityQueue<HotVideo>(11, Comparator.comparing(HotVideo::getHot)));
        long limit = 100; // 每次拿100个视频，进行分片查询
        long id = 0;
        // 查询出视频信息，每次从【id】开始查询【limit】个视频
        List<Video> videos = videoService.list(new LambdaQueryWrapper<Video>()
                .select(Video::getId, Video::getShareCount, Video::getHistoryCount, Video::getStartCount,
                        Video::getFavoritesCount, Video::getGmtCreated, Video::getTitle)
                .gt(Video::getId, id)
                .eq(Video::getAuditStatus, AuditStatus.SUCCESS)
                .eq(Video::getOpen, 0)
                .last("limit " + limit));

        while (!ObjectUtils.isEmpty(videos)) {
            for (Video video : videos) {
                Long shareCount = video.getShareCount(); // 视频分享总数 * 权重1
                Double historyCount = video.getHistoryCount() * 0.8; // 浏览总数 * 权重0.8
                Long startCount = video.getStartCount(); // 视频点赞总数 * 权重1
                Double favoritesCount = video.getFavoritesCount() * 1.5; // 视频收藏总数 * 权重1.5
                final Date date = new Date();
                long time = date.getTime() - video.getGmtCreated().getTime();

                final double v = weightRandom(); // 随机获取六位数的小数，用于去重
                final double hot = hot(shareCount + historyCount + startCount + favoritesCount + v, TimeUnit.MILLISECONDS.toDays(time));
                final HotVideo hotVideo = new HotVideo(hot, video.getId(), video.getTitle());

                topK.add(hotVideo);
            }
            id = videos.get(videos.size()-1).getId();
            videos = videoService.list(new LambdaQueryWrapper<Video>()
                    .select(Video::getId, Video::getShareCount, Video::getHistoryCount, Video::getStartCount,
                            Video::getFavoritesCount, Video::getGmtCreated, Video::getTitle)
                    .gt(Video::getId, id)
                    .last("limit " + limit));
        }
        final byte[] key = RedisConstant.HOT_RANK.getBytes();
        final List<HotVideo> hotVideos = topK.get();
        final Double minHot = hotVideos.get(hotVideos.size() - 1).getHot();
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (HotVideo hotVideo : hotVideos) {
                final Double hot = hotVideo.getHot();
                try {
                    hotVideo.setHot(null);
                    // 将【hotVideo】序列化写如redis
                    connection.zAdd(key, hot, jackson2JsonRedisSerializer.serialize(om.writeValueAsString(hotVideo)));
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
            }
            return null;
        });
        // 删除热度排行榜中热度比此次最低热度的视频还低的视频
        redisTemplate.opsForZSet().removeRangeByScore(RedisConstant.HOT_RANK, 0, minHot);
    }


    /**
     * 热门视频,没有热度排行榜实时且重要
     */
    @Scheduled(cron = "0 0 */3 * * ?") // 每三小时执行一次
    //@Scheduled(cron = "0/10 * * * * ?") // 每十秒执行一次
    public void hotVideo() {
        // 分片查询三天内的视频
        int limit = 30; // 一次最多查询多少条数据
        long id = 1;
        List<Video> videos = videoService.listNDaysAgeVideo(id, 30, limit);
        final Double hotLimit = settingService.list(null).get(0).getHotLimit();
        final Calendar calendar = Calendar.getInstance();
        int today = calendar.get(Calendar.DATE);

        while (!ObjectUtils.isEmpty(videos)) {
            final ArrayList<Long> hotVideo = new ArrayList<>();
            for (Video video : videos) {
                Long shareCount = video.getShareCount(); // 视频分享总数 * 权重1
                Double historyCount = video.getHistoryCount() * 0.8; // 浏览总数 * 权重0.8
                Long startCount = video.getStartCount(); // 视频点赞总数 * 权重1
                Double favoritesCount = video.getFavoritesCount() * 1.5; // 视频收藏总数 * 权重1.5
                final Date date = new Date();
                long time = date.getTime() - video.getGmtCreated().getTime();
                // 计算视频的热度
                final double hot = hot(shareCount + historyCount + startCount + favoritesCount, TimeUnit.MILLISECONDS.toDays(time));

                // 大于【系统hotLimit】热度说明是热门视频
                if (hot > hotLimit) {
                    hotVideo.add(video.getId());
                }
            }
            id = videos.get(videos.size() - 1).getId();
            videos = videoService.listNDaysAgeVideo(id, 30, limit);
            // RedisConstant.HOT_VIDEO + 今日日期 作为key  达到元素过期效果
            if (!ObjectUtils.isEmpty(hotVideo)) {
                final String key = RedisConstant.HOT_VIDEO + today;
                redisTemplate.opsForSet().add(key, hotVideo.toArray(new Object[hotVideo.size()]));
                redisTemplate.expire(key, 3, TimeUnit.DAYS); // 设置为3天过期
            }
        }
    }


    static double a = 0.011; // 时间对热度影响大小的调节位

    // 计算热度
    public static double hot(double weight, double t) {
        return weight * Math.exp(-a * t);
    }


    // 随机获取小数
    public double weightRandom() {
        int i = (int) ((Math.random() * 9 + 1) * 100000);
        return i / 1000000.0;
    }

}
